`timescale 1ns / 1ps

`define SLICE(BIT, IDX) (``BIT``) * (``IDX``) + (``BIT``) - 1 : (``BIT``) * (``IDX``)

module dwc
#(
    parameter N_IO      = 16,
    parameter N_CH      = 1280,
    parameter COL       = 40,
    parameter ROW       = 20,
    parameter BIT_I     = 8,
    parameter BIT_W     = 5,
    parameter BIT_O     = 16,
    parameter PADVAL    = 0,
    parameter PE_TYPE   = "LUT",    // DSP or LUT
    parameter ROM_FILE  = "L10_DW.mem",
    parameter ROM_TYPE  = "block",
    parameter ROM_LATENCY = 2,
    parameter RAM_TYPE  = "block",
    parameter RAM_LATENCY = 2
)
(
    input   clk,
    input   rst,
    
    input   i_vld,
    output  i_rdy,
    input   [N_IO * BIT_I -1 : 0]    i_data,
    
    output  o_vld,
    input   o_rdy,
    output  [N_IO * BIT_O -1 : 0]    o_data
);

localparam DATAPATH_LATENCY = 3;
localparam W_DT = N_IO * BIT_I;
localparam W_WT = N_IO * BIT_W;
localparam D_WT = N_CH * 9 / N_IO;

wire enable;
wire in_rdy;
wire in_vld;

reg  ld_acc;
wire ce_acc;
wire dy_vld;
wire dy_last;

wire ii_vld;
wire ii_last;
wire [W_DT -1 : 0]  ii_data;
wire wt_vld;
wire [W_WT -1 : 0]  wt_data;

assign enable = ~o_vld | o_rdy;
assign in_vld = ii_vld & wt_vld;
assign in_rdy = enable & in_vld;
assign ce_acc = enable & dy_vld;

im2col #(
    .N_IO        ( N_IO         ),
    .N_CH        ( N_CH         ),
    .BIT         ( BIT_I        ),
    .COL         ( COL          ),
    .ROW         ( ROW          ),
    .PADVAL      ( PADVAL       ),
    .RAM_TYPE    ( RAM_TYPE     ),
    .RAM_LATENCY ( RAM_LATENCY  )
)
inst_im2col (
    .clk                     ( clk      ),
    .rst                     ( rst      ),

    .i_rdy                   ( i_rdy    ),
    .i_vld                   ( i_vld    ),
    .i_data                  ( i_data   ),

    .o_rdy                   ( in_rdy   ),
    .o_vld                   ( ii_vld   ),
    .o_data                  ( ii_data  )
);

zq_rom_loader #(
    .WIDTH          (W_WT),
    .DEPTH          (D_WT),
    .ROM_FILE       (ROM_FILE),
    .ROM_TYPE       (ROM_TYPE),
    .ROM_LATENCY    (ROM_LATENCY)
) weight_loader
(
    .clk        (clk),
    .rst        (rst),

    .o_rdy      (in_rdy),
    .o_vld      (wt_vld),
    .o_data     (wt_data)
);

zq_counter #(
    .N  (9)
) inst_cnt_last
(
    .clk    (clk),
    .rst    (rst),
    .clken  (in_rdy),
    .last   (ii_last),
    .out    ()
);

shift_reg_rst #(
    .N          (DATAPATH_LATENCY - 1),
    .WIDTH      (2)
) inst_ctrl_line
(
    .clk        ( clk ),
    .rst        ( rst ),
    .clken      ( enable ),
    .i_data     ( {in_vld, ii_last} ),
    .o_data     ( {dy_vld, dy_last} )
);

always @(posedge clk)
begin
    if (rst)
        ld_acc <= 1'b0;
    else
    begin
        if (enable)
        begin
            if (dy_last)
                ld_acc <= 1'b0;
            else
                ld_acc <= ld_acc | dy_vld;
        end
    end
end

shift_reg_rst #(
    .N          (1),
    .WIDTH      (1)
) inst_ovld_line
(
    .clk        ( clk ),
    .rst        ( rst ),
    .clken      ( enable ),
    .i_data     ( dy_vld & dy_last ),
    .o_data     ( o_vld )
);

genvar i;
generate
    for (i = 0; i < N_IO; i = i + 1)
    begin
        if (PE_TYPE == "LUT") begin
            dwc_datapath #(
                .BIT_I ( BIT_I  ),
                .BIT_W ( BIT_W  ),
                .BIT_O ( BIT_O  )
            )
            inst_dwc_pe (
                .clk                     ( clk      ),
                .ce                      ( enable   ),
                .ce_acc                  ( ce_acc   ),
                .ld_acc                  ( ld_acc   ),

                .i_data                  ( ii_data[ `SLICE(BIT_I, i) ] ),
                .i_wigt                  ( wt_data[ `SLICE(BIT_W, i) ] ),

                .o_data                  (  o_data[ `SLICE(BIT_O, i) ] )
            );
        end
        else if (PE_TYPE == "DSP") begin
            wire [26 : 0] ain;
            wire [17 : 0] bin;
            wire [47 : 0] pout;

            assign ain = { 19'b0, ii_data[ `SLICE(BIT_I, i) ] };
            assign bin = { {13{wt_data[BIT_W * i + BIT_W - 1]}}, wt_data[ `SLICE(BIT_W, i) ] };
            assign o_data[ `SLICE(BIT_O, i) ] = pout[BIT_O - 1 : 0];

            dsp_dwc inst_dwc_pe (
                .CLK        ( clk       ),      // input wire CLK
                .SEL        ( ld_acc    ),      // input wire [0 : 0] SEL
                
                .A          ( ain       ),        // input wire [26 : 0] A
                .B          ( bin       ),        // input wire [17 : 0] B
                .P          ( pout      ),        // output wire [47 : 0] P

                .CEA4       ( enable    ),      // input wire CEA4
                .CEB4       ( enable    ),      // input wire CEB4
                .CEM        ( enable    ),      // input wire CEM
                .CEP        ( ce_acc    )       // input wire CEP
                // .CESEL5     ( enable    )       // input wire CESEL5
            );
        end
    end
endgenerate

endmodule
